﻿#region Apache 2.0 License header

//   Copyright 2012 Lesley van Zijl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. 

#endregion

using System;
using System.Collections.Generic;
using System.Reflection;

namespace FavourObjects
{
    /// <summary>
    /// Factory class for creating the FavourObjects
    /// </summary>
    public static class FavourObject
    {
        /// <summary>
        /// A simple cache for created types.
        /// </summary>
        private static readonly Dictionary<Tuple<Type, Type>, Type> CreatedTypesCache =
            new Dictionary<Tuple<Type, Type>, Type>();

        /// <summary>
        /// Creates an instance of the specified type with the specified args and then creates
        /// a Favour Object with the newly created instance as the target class.
        /// </summary>
        /// <typeparam name="TFavourInterface">Type of interface which the Favour Object needs to implement.</typeparam>
        /// <param name="targetType">The type of the target.</param>
        /// <param name="args">The arguments needed to create an instance of the targetType.</param>
        /// <returns>A Favour Object targetted towards the newly created type.</returns>
        public static TFavourInterface Create<TFavourInterface>(Type targetType, params object[] args)
            where TFavourInterface : class
        {
            Object target = Activator.CreateInstance(targetType,
                                                     BindingFlags.Public | BindingFlags.NonPublic |
                                                     BindingFlags.Instance,
                                                     null, args, null, null);

            return Create<TFavourInterface>(target);
        }

        /// <summary>
        /// Creates a favour object with the specified object as target class.
        /// </summary>
        /// <typeparam name="TFavourInterface">Type of interface which the Favour Object needs to implement.</typeparam>
        /// <param name="target">The instance to target</param>
        /// <returns>A Favour Object targetted towards the specified instance.</returns>
        public static TFavourInterface Create<TFavourInterface>(object target)
            where TFavourInterface : class
        {
            if (target == null)
            {
                throw new ArgumentNullException("target");
            }

            Type targetType = target.GetType();

            Type createdType;

            if (!CreatedTypesCache.TryGetValue(Tuple.Create(typeof (TFavourInterface), targetType), out createdType))
            {
                FavourInterfaceValidator.Validate(typeof (TFavourInterface), targetType);

                Type genericType =
                    typeof (FavourTypeBuilder<,>).MakeGenericType(new[] {typeof (TFavourInterface), targetType});
                var typeBuilder = (IFavourTypeBuilder) Activator.CreateInstance(genericType);

                createdType = typeBuilder.BuildType();

                CreatedTypesCache[Tuple.Create(typeof (TFavourInterface), targetType)] = createdType;
            }

            return (TFavourInterface) Activator.CreateInstance(createdType, target);
        }
    }
}